package org.groovymud.shell.security;

import java.io.IOException;
import java.security.Principal;
import java.util.Map;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;

import org.apache.log4j.Logger;
import org.groovymud.object.alive.Player;
import org.groovymud.object.registry.Registry;
import org.groovymud.shell.Shell;
import org.groovymud.shell.security.loginsteps.LoginStepManager;
import org.groovymud.shell.telnetd.MudShell;
import org.groovymud.shell.telnetd.ShellBridge;
import org.springframework.context.ApplicationContext;

/**
 * this little beast is the implementation of a JAAS login module
 * 
 * see http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASRefGuide.html
 * 
 * using JAAS should allow us to plug in modules a bit more easily by using a
 * standard framework we should be able to use platform login modules too if
 * required.
 * 
 * @author matt
 * 
 */
public class MudLoginModule implements LoginModule {

	private static final String LOGIN_STEP_MANAGER_BEAN = "loginStepManager";

	private Map options;

	// configurable option
	private boolean debug = false;

	// the authentication status
	private boolean succeeded = false;
	private boolean commitSucceeded = false;

	private static ApplicationContext applicationContext;

	private MudLoginModuleData data;

	private final static Logger logger = Logger.getLogger(MudLoginModule.class);

 	public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState, Map<String, ?> options) {
		this.options = options;
		if (options != null) {
			debug = "true".equalsIgnoreCase((String) options.get("debug"));
		}

         data = new MudLoginModuleData(subject, callbackHandler, sharedState, options);
	}

    public boolean login() throws LoginException {
		try {

			LoginStepManager manager = (LoginStepManager) applicationContext.getBean(LOGIN_STEP_MANAGER_BEAN);
			manager.getFirstStep().execute(data);

			setSucceeded(true);
			return true;

		} catch (IOException e) {
			logger.error(e, e);
			throw new LoginException(e.getMessage());
		} catch (UnsupportedCallbackException e) {
			logger.error(e, e);
			throw new LoginException(e.getMessage());
		}
	}

	public boolean abort() throws LoginException {
		if (!hasSucceeded()) {
			return false;
		}
		if (hasSucceeded() && hasCommitSucceeded()) {
			logout();
		} else {
			setSucceeded(false);
		}
		return true;
	}

	public boolean logout() throws LoginException {
		cleanup();

		setSucceeded(setCommitSucceeded(false));
		return true;
	}

	public boolean commit() throws LoginException {
		if (!hasSucceeded()) {
			cleanup();
			return false;
		}
		Player player = getPlayer();
		if (player != null) {

			getPlayerPrincipals().addAll(player.getSubject().getPrincipals());
			logger.info("player principals:");
			for (Principal pr : getPlayerPrincipals()) {
				logger.info(pr.getName());
			}

			getSubject().getPrincipals().addAll(getPlayerPrincipals());
			player.setSubject(getSubject()); // update the players principals
            if (!data.isGuest()) {
				PlayerCredentials playerCredentials = new PlayerCredentials(getUsername(), String.valueOf(getPassword()));

				getSubject().getPrivateCredentials().add(playerCredentials);
				player.setPlayerCredentials(playerCredentials);
			}
			setCommitSucceeded(true);

		} else {
            cleanup();
			throw new LoginException("player was null");
		}
		return true;
	}

	protected void cleanup() {
		data.username = null;
		data.password = null;
		data.dupe = null;
		data.player = null;
		data.playerPrincipals = null;
		data.subject = null;
		options = null;
		data.sharedState = null;
	}

	public Player getPlayer() {
		return data.player;
	}

	public void setPlayer(Player player) {
		this.data.player = player;
	}

	public CallbackHandler getCallbackHandler() {
		return data.callbackHandler;
	}

	public String getUsername() {
		return data.username;
	}

	public void setUsername(String username) {
		this.data.username = username;
	}

	public char[] getPassword() {
		return data.password;
	}

	public void setPassword(char[] password) {
		this.data.password = password;
	}

	public Subject getSubject() {
		return data.subject;
	}

	public void setSubject(Subject subject) {
		this.data.subject = subject;
	}

	public Player getDupe() {
		return data.dupe;
	}

	public void setDupe(Player dupe) {
		this.data.dupe = dupe;
	}

	public void setCallbackHandler(CallbackHandler callbackHandler) {
		this.data.callbackHandler = callbackHandler;
	}

	public Set<Principal> getPlayerPrincipals() {
		return data.playerPrincipals;
	}

	public void setSucceeded(boolean succeeded) {
		this.succeeded = succeeded;
	}

	public boolean hasSucceeded() {
		return succeeded;
	}

	public boolean setCommitSucceeded(boolean commitSucceeded) {
		this.commitSucceeded = commitSucceeded;
		return commitSucceeded;
	}

	public boolean hasCommitSucceeded() {
		return commitSucceeded;
	}

	/**
	 * @return the applicationContext
	 */
	public ApplicationContext getApplicationContext() {
		return applicationContext;
	}

	/**
	 * @param applicationContext the applicationContext to set
	 */
	public static void setApplicationContext(ApplicationContext applicationContext) {
		MudLoginModule.applicationContext = applicationContext;
	}

}
